# -*- coding: utf-8 -*-
# @Time   : 2022-06-06 17:51:04
# @Author : 搬砖客


import re
import time
import traceback

import jsonpath
import requests
from utils.logging_utils.log_util import write_log, write_error_log, write_warning_log
from utils.process_files_utils.yaml_util import YamlUtil
from typing import Text, Dict, Union
from utils.requests_utils.replace_load import replace_load
from utils.requests_utils.assert_util import AssertUtil


class RequestUtil:
    session = requests.session()

    def __init__(self):
        self.base_url = None
        self.last_url = ""
        self.last_method = None
        self.last_headers = {}

    # 规范功能测试YAML文件的写法
    def analysis_yaml(self, args):
        """

        :param args:
        :return: 分析测试用例，返回接口响应内容、断言、提取
        """
        name = None
        method = None
        url = None
        headers = None
        files = None
        args_keys = None

        try:
            # 1.必须有五个一级关键词：name、base_url、request、validate、assert_sql
            args_keys = dict(args).keys()
            if all(key in args_keys for key in ['name', 'base_url', 'request', 'validate', 'assert_sql']):
                # 2.在request一级关键字下必须包含两个二级关键字：method、url
                request_keys = dict(args['request']).keys()
                if 'method' in request_keys and 'url' in request_keys:
                    self.base_url = args['base_url']
                    name = args['name']
                    # 参数(params、data、json)、请求头、文件上传无法进行约束
                    method = args['request']['method']
                    # 获得method的值后，将对应键值删除,下面的del同理
                    # 为了把method、url、headers、files从args['request']中去掉之后，把剩下的传给kwargs
                    del args['request']['method']
                    url = args['request']['url']
                    del args['request']['url']
                    headers = None
                    if jsonpath.jsonpath(args, '$..headers'):
                        headers = args['request']['headers']
                        del args['request']['headers']
                    files = None
                    if jsonpath.jsonpath(args, '$..files'):
                        files = args['request']['files']
                        for key, value in dict(files).items():
                            files[key] = open(value, 'rb')
                        del args['request']['files']
                else:
                    write_warning_log("测试用例中在request一级关键字下必须包含两个二级关键字：method、url")
            else:
                write_warning_log("测试用例中必须有五个一级关键词：name、base_url、request、validate、assert_sql")
        except Exception as e:
            write_log("错误用例名称为：{}".format(args['name']))
            write_warning_log("分析yaml用例文件异常，异常信息为：%s" % str(traceback.format_exc()))
        res = None
        return_json = None
        return_text = None
        status_code = None
        write_log(f"--------------------测试用例【{name}】开始执行---------------------")
        # 用例运行之前的操作
        if "setup" in dict(args).keys() and dict(args)['setup'] is not None:
            write_log("--------------------进行用例的前置操作--------------------")
            try:
                replace_load(dict(args['setup']))
            except Exception as e:
                write_warning_log(f"进行前置操作出错，错误信息为：{e}")
        try:
            res = self.send_request(name=name, method=method, url=url, headers=headers, files=files,
                                    **args['request'])
            return_json = res.json()
            return_text = res.text
            status_code = res.status_code
        except Exception as e:
            write_warning_log(f"接口请求失败，原因为：{e}")

        """
        1.进行断言的封装(需要放在提取之后，这样断言失败，还是会先去提取)括号中的，忘了为啥这么写了
        2.改为了先接口断言，返回接口内容，   
        3.提取接口内容
        4.进行数据库断言   
        """

        try:
            # 这边加了响应时间断言
            res_time = round(float(res.elapsed.total_seconds() * 1000))
            write_log("接口响应时间为：{} ms".format(res_time))

            # 智能等待时间
            try:
                wait_time = args['wait_time']
                time.sleep(wait_time)
                write_log(f"智能等待时间为:{wait_time}秒")
            except:
                pass
            # 接口返回断言
            expect_result = args['validate']
            real_result = return_json
            assert_time = YamlUtil.read_config_yaml("switch", "assert_time")
            AssertUtil.validate_result(expect_result, real_result, status_code, res_time, assert_time)

            # 提取接口关联的变量,既要支持正则提取，也要支持json提取
            try:
                if 'extract' in args_keys:
                    for key, value in dict(args['extract']).items():
                        write_log(f"key的值为：{key}")
                        # 正则提取
                        # 这边写yaml的时候，键值对之间不要有空格
                        # 处理空格问题
                        value = str(value).replace(" ", "")
                        if '(.+?)' in value or '(.*?)' in value:
                            ze_values = re.search(value, return_text)
                            if ze_values:
                                try:
                                    # 如果提示的结果为数值，就需要将返回的str转换为数值型
                                    extract_data = {key: eval(ze_values.group(1))}
                                    YamlUtil.write_extract_yaml(extract_data)
                                    write_log("提取元素成功，结果为：{}".format(extract_data))
                                except:
                                    extract_data = {key: ze_values.group(1)}
                                    YamlUtil.write_extract_yaml(extract_data)
                                    write_log("提取元素成功，结果为：{}".format(extract_data))

                            else:
                                write_log("提取元素失败，想要提取的Key为：{}".format(key))
                                write_log("ze_values的值为：{}".format(key))
                        # jsonpath提取
                        else:
                            jsonpath_value = jsonpath.jsonpath(return_json, value)[0]
                            extract_data = {key: jsonpath_value}
                            YamlUtil.write_extract_yaml(extract_data)
                            write_log("提取元素成功，结果为：{}".format(extract_data))
                # todo 将请求参数的某一下值放入extract.yaml文件
            except Exception as e:
                write_warning_log(f"extract提取内容失败，原因为：{e}")

            # sql断言,config中sql为True则进行数据库断言
            if YamlUtil.read_config_yaml("switch", "sql"):
                expect_sql = args['assert_sql']
                AssertUtil.validate_sql_result(expect_sql)
                write_log("----------------------测试用例执行完毕-------------------\n")

        except Exception as e:
            write_log("----------------------测试用例执行完毕-------------------")
            write_error_log(f"测试用例断言失败，失败信息如下:{e}")

        # 用例运行之后的操作
        if "teardown" in dict(args).keys() and dict(args)['teardown'] is not None:
            write_log("--------------------进行用例的后置操作--------------------")
            try:
                replace_load(dict(args['teardown']))
            except Exception as e:
                write_warning_log(f"进行后置操作出错，错误信息为：{e}")

    # 通过session保持登录的请求，yaml通过${}关联变量和函数
    def send_request(self, name: Text, method: Text, url: Text, headers=None, file=None, **kwargs):
        """

        :param name: 测试用例名称
        :param method: 接口请求方式
        :param url: 接口url
        :param headers: 接口请求头
        :param file: 接口上传文件
        :param kwargs: 接口请求体
        :return: 分析测试用例，并请求
        """
        try:
            # 处理请求头，将method输入的值都转换为小写
            self.last_method = str(method).lower()
            # 处理基础路径
            self.last_url = replace_load(self.base_url) + str(replace_load(url))
            # 处理请求头
            if headers and isinstance(headers, dict):
                self.last_headers = replace_load(headers)
            # 重点，处理请求体：可能是params、data、json
            for key, value in kwargs.items():
                if key in ['params', 'data', 'json']:
                    kwargs[key] = replace_load(value)
            # 收集日志
            write_log("--------------------接口请求开始---------------------")
            write_log("用例名称为：{}".format(name))
            write_log("请求方式为：{}".format(self.last_method))
            write_log("请求路径为：{}".format(self.last_url))
            write_log("请求头为：{}".format(self.last_headers))
            if "params" in kwargs.keys():
                write_log("请求参数格式为params，内容为：{}".format(kwargs['params']))
            if "data" in kwargs.keys():
                write_log("请求参数格式为data，内容为：{}".format(kwargs['data']))
            if "json" in kwargs.keys():
                write_log("请求参数格式为json，内容为：{}".format(kwargs['json']))
            write_log("文件上传：{}".format(file))

            # 发送请求
            res = RequestUtil.session.request(method=self.last_method, url=self.last_url,
                                              headers=self.last_headers, **kwargs)
            return res
        except Exception as e:
            write_warning_log("错误用例名称为：%s,发送请求异常，异常信息为：%s" % (name, str(traceback.format_exc())))
